/*

A decorator class to encapsulate files
	- opening, reading, writing, saving
	- editors
	- searching
	- autowatch

 */
 
#ifndef MAX_FILE_H
#define MAX_FILE_H

#include "max.cpp.h"

template <class T>
class MaxFile
{
public:
	
	// typedefs
	typedef void (T::*maxFileCallback)(char * script);
	
	static void initMaxFileClass(t_class * c, maxFileCallback);

protected:

	MaxFile();
	~MaxFile();
	
	void filechanged(char * filename, short path);
	void writefile(t_symbol *s, short argc, t_atom *argv);
	void findpath(char * path, const char * desiredpath);	

	static maxFileCallback maxFileUserCallback;

	t_symbol * maxFileName;
	short	maxFileVolume;		// file system volume where file is

	long	maxFileAutoWatch;			// flag for reloading script if updated
	void	* maxFileWatcher;		// watches if script is updated (autowatch)
	
	char	**maxFileTextHandle;		// handle to script string
	long	maxFileTextSize;
	
	t_ed *	maxFileEditor;
	char *	maxFileScript;
	unsigned int maxFileScriptSize;
	unsigned int maxFileFontSize;
	long	maxFileAutoSave;	// automatically save file on editor close when set to 1
	
	void doreadfile(char *filename);
	void dowritefile(char * filename);
	void dosetscript(char * text, long size = 0);
	
	static void maxFileNameSetter(T *x, void *attr, long ac, t_atom *av);
	
	static void static_write(T * x, t_symbol *s, short argc, t_atom *argv);
	
	static void maxFileAutoWatchSetter(T *x, void *attr, long ac, t_atom *av);
	static void maxFileAutoWatchChanged(T * x, char *filename, short path);
	
	static void dblclick(T * x);
	static void * edsave(T * x, char **text, long size, char *filename, short vol);
	static void edclose(T * x, char ** text, long size);
	static void okclose(T * x, char *prompt, short *result);
	static void settext(T * x, t_symbol *s, short argc, t_atom *argv);

};

template <class T>
void (T::*MaxFile<T>::maxFileUserCallback)(char *);

template <class T>
void MaxFile<T>::initMaxFileClass(t_class * c, maxFileCallback cb)
{
	maxFileUserCallback = cb;
	
	// a dummy class pointer in order to use in offsetof_in_object
	T * dummy;
	
	// @fontsize
	class_addattr(c, (t_object *)attr_offset_new(
		"fontsize",				// name
		_sym_long,				// type
		0,						// attrflags, 
		(method)0L,				// getter
		(method)0L,				// setter
		calcoffset_cpp(dummy, maxFileFontSize)	// position within class
	));
	
	// @autosave
	class_addattr(c, (t_object *)attr_offset_new(
		"autosave",				// name
		_sym_long,				// type
		0,						// attrflags, 
		(method)0L,				// getter
		(method)0L,				// setter
		calcoffset_cpp(dummy, maxFileAutoSave)	// position within class
	));
	
//	// @autowatch
//	class_addattr(c, (t_object *)attr_offset_new(
//		"autowatch",				// name
//		_sym_long,				// type
//		0,						// attrflags, 
//		(method)0L,				// getter
//		(method)0L,				// setter
//		calcoffset_cpp(dummy, maxFileAutoSave)	// position within class
//	));
	
	class_addattr(c, (t_object *)attr_offset_new(
		"autowatch",				// name
		_sym_long,				// type
		ATTR_GET_DEFER_LOW | ATTR_SET_USURP_LOW, // attrflags, 
		(method)0L,				// getter
		(method)maxFileAutoWatchSetter,	// setter
		calcoffset_cpp(dummy, maxFileAutoWatch)	// position within class
	));
	
	class_addattr(c, (t_object *)attr_offset_new(
		"file",				// name
		_sym_symbol,			// type
		ATTR_GET_DEFER_LOW | ATTR_SET_USURP_LOW, // attrflags, 
		(method)0L,				// getter
		(method)maxFileNameSetter,	// setter
		calcoffset_cpp(dummy, maxFileName)	// position within class
	));
	
	// file IO
	class_addmethod(c, (method)T::static_write, "write",	A_GIMME,0);	
	class_addmethod(c, (method)T::maxFileAutoWatchChanged, "filechanged", A_CANT, 0);
	
	// text editor	
	class_addmethod(c, (method)T::dblclick, "dblclick", A_CANT, 0);
	class_addmethod(c, (method)T::dblclick, "open", 0);
	class_addmethod(c, (method)T::edclose, "edclose",	A_CANT, 0);
	class_addmethod(c, (method)T::edsave, "edsave",	A_CANT, 0);
	class_addmethod(c, (method)T::okclose, "okclose", A_CANT, 0);
	class_addmethod(c, (method)T::settext,	"settext",	A_GIMME,0);
}

template <class T>
MaxFile<T> :: MaxFile()
{
	DEBUG("new MaxFile %p sizeptr %p", this, &this->maxFileTextSize);
	
	maxFileName = 0;
	maxFileEditor = 0;
	maxFileScript = 0;
	maxFileScriptSize = 0;
	maxFileFontSize = 13;
	maxFileAutoSave = 0;
	maxFileAutoWatch = 0;
}

template <class T>
MaxFile<T> :: ~MaxFile()
{
	if (maxFileScript) {	
		sysmem_freeptr((void *)maxFileScript);
		maxFileScript = 0;
	}
	if (maxFileTextHandle) {
		sysmem_lockhandle(maxFileTextHandle, false);
		sysmem_freehandle(maxFileTextHandle);
	}
}


template <class T>
void MaxFile<T> :: maxFileNameSetter(T *x, void *attr, long argc, t_atom *argv)
{
	long outtype;
	char filename[256];
	long type = 'TEXT'; 

	// get the filename requested:
	if (argc && argv[0].a_type == A_SYM)
	{		
		strcpy(filename, argv[0].a_w.w_sym->s_name);
		if (locatefile_extended(filename, &(x->maxFileVolume), &outtype, &type, 1)) {
			error("can't find %s", filename); 
			return; //not found
		}
	} else {
//		if (open_dialog(filename, &path, &outtype, &type, 1))
		if (open_dialog(filename,  &(x->maxFileVolume), &outtype, 0L, 0)) // allow all types of files
			return; //user cancelled
	}
	
	x->doreadfile(filename);
}

template <class T>
void MaxFile<T> :: maxFileAutoWatchSetter(T *x, void *attr, long ac, t_atom *av)
{
	if(ac&&av) 
	{
		x->maxFileAutoWatch = av->a_type == A_LONG && av->a_w.w_long;
		DEBUG("set autowatch to %i", (int)x->maxFileAutoWatch);
		
		if (x->maxFileAutoWatch && x->maxFileName && x->maxFileName->s_name) 
		{
			if (x->maxFileWatcher) {
				freeobject((t_object *)x->maxFileWatcher);
			}
			x->maxFileWatcher = filewatcher_new((t_object *)x, x->maxFileVolume, x->maxFileName->s_name);
			filewatcher_start(x->maxFileWatcher);		
		} 
		else 
		{
			if (x->maxFileWatcher) {
				freeobject((t_object *)x->maxFileWatcher);
			}
			x->maxFileWatcher = NULL;
		}
	}
}

template <class T>
void MaxFile<T> :: maxFileAutoWatchChanged(T * x, char *filename, short path)
{
	if (filename) 
	{
		post("autowatch: file %s changed", filename);
		x->maxFileVolume = path;
		x->doreadfile(filename);
	}
}

//template <class T>
//void MaxFile<T> :: maxFileAutoWatchChanged(T * x, t_symbol *s, short argc, t_atom *argv)
//{
//	post("autowatch: file %s changed", x->maxFile->s_name);
//	post("args %i sym %s", argc, s->s_name);
//	
//	
//	//if (!maxFileAutoWatch)
////		return;
////		
////	if (!strcmp(filename, maxFile->s_name) && maxFileVolume == path) 
////	{
////		doreadfile(maxFile->s_name);
////	}
//}

template <class T>
void MaxFile<T> :: writefile(t_symbol *s, short argc, t_atom *argv)
{
	char tmpfilename[256];
	long type_chosen, thistype = 'TEXT';
	
	if (argc && argv[0].a_type == A_SYM)
	{		
		strcpy(tmpfilename, argv[0].a_w.w_sym->s_name);
	} else {
		//if (saveas_dialog(&x->s_name, &x->path, &type)) 
		strcpy(tmpfilename, maxFileName->s_name);	// useful default for the saveas dialog
		if (saveasdialog_extended(tmpfilename, &(maxFileVolume), &type_chosen, &thistype, 1))
			return; //user cancelled

	}
	
	dowritefile(tmpfilename);
}

template <class T>
void MaxFile<T> :: dowritefile(char * filename)
{
	short err;
	t_handle script_handle;
	t_filehandle fh;
	long thistype = 'TEXT';
	
	post("saving file %s", filename);
	
	err = path_createsysfile(filename, (maxFileVolume), thistype, &fh);  
	if (err) {       
		fh = 0;      
		error("error %d creating file", err); 
		return;
	}
	
	script_handle = sysmem_newhandle(0);
	sysmem_ptrandhand (maxFileScript, script_handle, maxFileScriptSize); 

	err = sysfile_writetextfile(fh, script_handle, TEXT_LB_UNIX); 
	if (err) {       
		fh = 0;      
		error("error %d writing file", err); 
		return;
	}
	
	sysmem_freehandle(script_handle);
	sysfile_seteof(fh, maxFileScriptSize); 
	sysfile_close(fh);
	
	maxFileName = gensym(filename);

	return;	
}

template <class T>
void MaxFile<T> :: doreadfile(char * filename)
{
	short err;
	long size, count;
	t_filehandle fh;
	t_handle script_handle;
	
	post("open file %s", filename);
	
	err = path_opensysfile(filename, maxFileVolume, &fh, READ_PERM);
	if (err) {
		error("error %d opening file %s", err, filename);
		return;
	}
	
	maxFileName = gensym(filename);
	
	sysfile_geteof(fh, &size);
	if (maxFileScript) {	
		sysmem_freeptr((void *)maxFileScript);
		maxFileScript = 0;
	}
	if (!(maxFileScript = (char *)sysmem_newptr(size)) || !(script_handle = sysmem_newhandle(size))) {
		error("%s too big to read", filename);
		return;
	} 
	else 
	{
		if (maxFileTextHandle) {
			sysmem_lockhandle(maxFileTextHandle, false);
			sysmem_freehandle(maxFileTextHandle);
		}
		maxFileTextHandle = sysmem_newhandle(0);
		sysfile_readtextfile(fh, maxFileTextHandle, 0, TEXT_LB_NATIVE);
		count = sysmem_handlesize(maxFileTextHandle);
		
		sysmem_resizehandle(maxFileTextHandle, count + 1);
		(*maxFileTextHandle)[count] = 0; //NULL terminate, '\0'?
		maxFileScriptSize = count;
		
		//zap_gremlins(*x->texthandle, count);
		strcpy(maxFileScript, *maxFileTextHandle);
	}
	sysmem_freehandle(script_handle);
	sysfile_close(fh);
	
	// install filewatcher if necessary:
	if (maxFileAutoWatch) {
		t_atom a;
		SETLONG(&a, 1);
		maxFileAutoWatchSetter((T *)this, NULL, 1, &a);	
	}
	
	// now call the user handler
	(((T *)this)->*maxFileUserCallback)(maxFileScript);
}

template <class T>
inline void MaxFile<T> :: dosetscript(char * text, long size)  
{ 
	if (size == 0) size = strlen(text);
	
	if (maxFileScript) {	
		sysmem_freeptr((void *)maxFileScript);
		maxFileScript = 0;
	}
	
	// update internal script:
	maxFileScriptSize = size;
	maxFileScript = (char *)sysmem_newptr(size * sizeof(char));
	strncpy(maxFileScript, text, size);
	maxFileScript[size] = 0; //NULL terminate, '\0'?
	
	if (maxFileName && maxFileName->s_name) {
		dowritefile(maxFileName->s_name);
	}
	
	// now call the user handler:
	(((T *)this)->*maxFileUserCallback)(maxFileScript); 
}

template <class T>
void MaxFile<T> :: findpath(char * path, const char * desiredpath)
{
	short app_path;
	char total_path[512];
	char *absolute;
	
	app_path = path_getapppath();
	path_topathname(app_path, "", total_path);
	absolute = strstr(total_path, ":/");
	absolute++;

	strcat(absolute, desiredpath); //"Cycling '74/lua/luamods/");
	strcpy(path, absolute);
}

template <class T>
inline void MaxFile<T> :: static_write(T * x, t_symbol *s, short argc, t_atom *argv)  
{ x->writefile(s, argc, argv); }

template <class T>
void MaxFile<T> :: dblclick(T * x)
{
	DEBUG("dblclick");
	
	if (x->maxFileEditor) {
		ed_vis(x->maxFileEditor);
	} else {
		x->maxFileEditor = (t_ed *)ed_new((t_object *)((T *)x));
		if (!x->maxFileEditor) {
			return;
		}

		//maxFileEditor->e_wind->w_scratch = 0;	
		object_method(x->maxFileEditor->e_wind, gensym("wsize"), 400, 600);
		mess2((t_object *)x->maxFileEditor, gensym("font"), (void*)x->maxFileFontSize, (void *)-1);
	}
		
	if (x->maxFileName && x->maxFileName->s_name) {
		wind_settitle(x->maxFileEditor->e_wind, x->maxFileName->s_name, 0);
	}
	if (x->maxFileScript) {
			ed_settext (x->maxFileEditor, &x->maxFileScript, x->maxFileScriptSize); 
	}
}

template <class T>
void * MaxFile<T> :: edsave(T * x, char **text, long size, char *filename, short vol)
{
	DEBUG("edsave");
	
	if (x->maxFileScript) 
	{
		sysmem_freeptr((void *)x->maxFileScript);
		x->maxFileScript = 0;
	}
	
	// update new filepath:
	x->maxFileVolume = vol;
	x->maxFileName = gensym(filename);
	
	x->dosetscript(*text, size);
	
	// let Max save the file:
	return 0;
}


template <class T>
void MaxFile<T> :: edclose(T * x, char ** text, long size)  
{ 
	if (x->maxFileName && x->maxFileAutoSave) 
	{
		if (x->maxFileEditor->e_wind->w_dirty) 
		{
			// autosave mode is on and the file has been changed
			post("autosaved %s", x->maxFileName->s_name);
			
			x->dosetscript(*text, size);
		}
	}
	
	x->maxFileEditor = 0; 
}

template <class T>
void MaxFile<T> :: okclose(T * x, char *prompt, short *result)  
{ 
	// skip 'save changes' if a file is already open and autosave is on:
	if (x->maxFileName && x->maxFileName->s_name && x->maxFileAutoSave)
	{
		*result = 3;
	} else {
		*result = 0;	// default save dialog
	}
}

template <class T>
inline void MaxFile<T> :: settext(T * x, t_symbol *s, short argc, t_atom *argv)
{
	if (argc && argv[0].a_type == A_SYM)
	{
		x->dosetscript(argv[0].a_w.w_sym->s_name);
	}
}

#endif